/*
 * Copyright © 2017-2023 TemplateFactory(xiaoymin@foxmail.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


package com.github.xiaoymin.template.config;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.system.SystemUtil;
import com.github.xiaoymin.template.core.IntrospectedColumn;
import com.github.xiaoymin.template.core.IntrospectedTable;
import com.github.xiaoymin.template.factory.TemplateFactory;
import com.github.xiaoymin.template.internel.ObjectFactory;
import org.apache.metamodel.DataContext;
import org.apache.metamodel.jdbc.JdbcDataContext;
import org.apache.metamodel.schema.*;
import org.dom4j.Attribute;
import org.dom4j.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.InvocationTargetException;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;

/***
 *
 * @since template-engine 1.0
 * @author <a href="mailto:xiaoymin@foxmail.com">xiaoymin@foxmail.com</a> 
 * 2020/06/06 15:20
 */
public class Context {
    
    Logger logger = LoggerFactory.getLogger(Context.class);
    
    private String id;
    /**
     * JDBC连接信息
     */
    private JdbcConnectionConfiguration jdbcConnectionConfiguration;
    /**
     * 数据库表集合
     */
    private List<IntrospectedTable> introspectedTables;
    
    /**
     * 模板列表
     */
    private List<TemplateFactoryConfiguration> templateConfigurations;
    
    public Context() {
        this.templateConfigurations = new ArrayList<>();
        this.introspectedTables = new ArrayList<>();
    }
    public Context(String id) {
        this.id = id;
        this.templateConfigurations = new ArrayList<>();
        this.introspectedTables = new ArrayList<>();
    }
    
    /**
     * 识别JDBC的配置
     * @param element
     */
    public void applyJdbcConfiguration(Element element) {
        if (element == null) {
            throw new IllegalArgumentException("当前Context接口中不包含JDBC配置信息");
        }
        Element jdbcEle = element.element("jdbcConnection");
        if (jdbcEle == null) {
            throw new IllegalArgumentException("当前Context接口中不包含JDBC配置信息");
        }
        this.jdbcConnectionConfiguration = new JdbcConnectionConfiguration();
        this.jdbcConnectionConfiguration.setDriverClass(jdbcEle.attributeValue("driverClass"));
        this.jdbcConnectionConfiguration.setConnectionURL(jdbcEle.attributeValue("connectionURL"));
        // 从环境变量读取，保护数据安全
        String envUserId = SystemUtil.get(jdbcEle.attributeValue("userId"));
        if (StrUtil.isBlank(envUserId)) {
            this.jdbcConnectionConfiguration.setUserId(jdbcEle.attributeValue("userId"));
        } else {
            this.jdbcConnectionConfiguration.setUserId(envUserId);
        }
        String envPwd = SystemUtil.get(jdbcEle.attributeValue("password"));
        if (StrUtil.isBlank(envPwd)) {
            this.jdbcConnectionConfiguration.setPassword(jdbcEle.attributeValue("password"));
        } else {
            this.jdbcConnectionConfiguration.setPassword(envPwd);
        }
        logger.info("jdbcConnectionConfiguration:{}", this.jdbcConnectionConfiguration.toString());
    }
    
    public void initJdbcConnections() {
        if (this.jdbcConnectionConfiguration == null) {
            logger.info("当前JDBC连接信息为空");
            return;
        }
        Connection connection = null;
        try {
            JdbcConnectionConfiguration jdbcConnectionConfiguration = this.getJdbcConnectionConfiguration();
            if (jdbcConnectionConfiguration != null) {
                Class<?> driverClass = ObjectFactory.externalClassForName(jdbcConnectionConfiguration.getDriverClass());
                Driver driver = (Driver) driverClass.getConstructor().newInstance();
                jdbcConnectionConfiguration.addProperty("user", jdbcConnectionConfiguration.getUserId());
                jdbcConnectionConfiguration.addProperty("password", jdbcConnectionConfiguration.getPassword());
                connection = driver.connect(jdbcConnectionConfiguration.getConnectionURL(), jdbcConnectionConfiguration.getProperties());
                DataContext dataContext = new JdbcDataContext(connection, new TableType[]{TableType.TABLE}, null);
                Schema schema = dataContext.getDefaultSchema();
                List<Table> tableList = schema.getTables();
                for (Table table : tableList) {
                    IntrospectedTable introspectedTable = new IntrospectedTable();
                    introspectedTable.setRemarks(table.getRemarks());
                    introspectedTable.setTableName(table.getName());
                    List<IntrospectedColumn> introspectedColumns = new ArrayList<>();
                    List<Column> columns = table.getColumns();
                    for (Column column : columns) {
                        // logger.info("columnName:{},nativeType:{}",column.getName(),column.getNativeType());
                        IntrospectedColumn introspectedColumn = new IntrospectedColumn();
                        introspectedColumn.setOriginalColumnName(column.getName());
                        introspectedColumn.setJavaColumnName(StrUtil.toCamelCase(introspectedColumn.getOriginalColumnName()));
                        introspectedColumn.setJavaGetterName(StrUtil.genGetter(introspectedColumn.getJavaColumnName()));
                        introspectedColumn.setJavaSetterName(StrUtil.genSetter(introspectedColumn.getJavaColumnName()));
                        introspectedColumn.setJavaColumnUpperName(StrUtil.upperFirst(introspectedColumn.getJavaColumnName()));
                        introspectedColumn.setDescription(column.getRemarks());
                        // 是否必传
                        introspectedColumn.setRequire(!column.isNullable());
                        // 类型
                        introspectedColumn.setJavaType(column.getType().getJavaEquivalentClass().getSimpleName());
                        // 判断时间类型,更精确使用JDK8中的日期类型
                        ColumnType columnType = column.getType();
                        if (columnType == ColumnType.DATE || columnType == ColumnType.TIME || columnType == ColumnType.TIMESTAMP) {
                            // 判断driver类型,区分不同的数据库类型
                            // 仅支持mysql或者mariadb
                            if (driver instanceof com.mysql.jdbc.Driver || driver instanceof org.mariadb.jdbc.Driver) {
                                String nativeType = column.getNativeType();
                                if (StrUtil.equalsIgnoreCase(nativeType, "datetime")) {
                                    introspectedColumn.setJavaType("LocalDateTime");
                                } else if (StrUtil.equalsIgnoreCase(nativeType, "date")) {
                                    introspectedColumn.setJavaType("LocalDate");
                                } else if (StrUtil.equalsIgnoreCase(nativeType, "timestamp")) {
                                    introspectedColumn.setJavaType("LocalDateTime");
                                }
                            }
                        } else if (columnType == ColumnType.DECIMAL || columnType == ColumnType.DOUBLE || columnType == ColumnType.FLOAT) {
                            introspectedColumn.setJavaType("BigDecimal");
                        }
                        introspectedColumns.add(introspectedColumn);
                    }
                    introspectedTable.setColumns(introspectedColumns);
                    this.introspectedTables.add(introspectedTable);
                }
            }
        } catch (Exception e) {
            logger.error("异常:" + e.getMessage(), e);
        } finally {
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
        }
    }
    
    public IntrospectedTable filterByName(String tableName) {
        Optional<IntrospectedTable> introspectedTableOptional = this.introspectedTables.stream().filter(introspectedTable -> StrUtil.equals(introspectedTable.getTableName(), tableName)).findFirst();
        if (introspectedTableOptional.isPresent()) {
            return introspectedTableOptional.get();
        }
        return null;
    }
    
    /**
     * 识别Template
     * @param element
     */
    public void applyTemplates(Element element, Configuration configuration) {
        if (element == null) {
            logger.info("当前Context接口中不包含Template配置信息");
            return;
        }
        try {
            Iterator<Element> templateElements = element.elementIterator("templateFactory");
            if (templateElements != null) {
                logger.info("包含Template模板");
                while (templateElements.hasNext()) {
                    Element templateEle = templateElements.next();
                    TemplateFactoryConfiguration templateConfiguration = new TemplateFactoryConfiguration();
                    templateConfiguration.setModule(templateEle.attributeValue("module"));
                    templateConfiguration.setTableName(templateEle.attributeValue("tableName"));
                    templateConfiguration.setAlias(templateEle.attributeValue("alias"));
                    templateConfiguration.setType(templateEle.attributeValue("type"));
                    List<Attribute> attributes = templateEle.attributes();
                    if (CollectionUtil.isNotEmpty(attributes)) {
                        for (Attribute attribute : attributes) {
                            if (StrUtil.isNotBlank(attribute.getName())) {
                                templateConfiguration.addProperty(attribute.getName(), attribute.getValue());
                            }
                        }
                    }
                    // 判断插件别名是否包含
                    if (!configuration.containtAliasName(templateConfiguration.getAlias())) {
                        // 不包含
                        TemplateFactory templateEnginePlugin = (TemplateFactory) ClassLoader.getSystemClassLoader().loadClass(templateConfiguration.getType()).getConstructor().newInstance();
                        configuration.addPlugin(templateEnginePlugin);
                    }
                    // 查询子模板
                    List<TemplateConfiguration> childrens = new ArrayList<>();
                    Iterator<Element> tmpElements = templateEle.elementIterator("template");
                    if (tmpElements != null) {
                        while (tmpElements.hasNext()) {
                            Element childEle = tmpElements.next();
                            TemplateConfiguration tmple = new TemplateConfiguration();
                            tmple.setModule(templateConfiguration.getModule());
                            tmple.setPath(childEle.attributeValue("path"));
                            tmple.setDomainObjectName(childEle.attributeValue("domainObjectName"));
                            tmple.setPackageName(childEle.attributeValue("packageName"));
                            tmple.setAlias(childEle.attributeValue("alias"));
                            List<Attribute> attributeList = childEle.attributes();
                            // 添加任意属性配置
                            if (CollectionUtil.isNotEmpty(attributeList)) {
                                for (Attribute attribute : attributeList) {
                                    if (StrUtil.isNotBlank(attribute.getName())) {
                                        tmple.addProperty(attribute.getName(), attribute.getValue());
                                    }
                                }
                            }
                            childrens.add(tmple);
                        }
                    }
                    templateConfiguration.setTemplateConfigurations(childrens);
                    logger.info("templateConfiguration:{}", templateConfiguration.toString());
                    this.templateConfigurations.add(templateConfiguration);
                }
            }
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }
    
    public String getId() {
        return id;
    }
    
    public void setId(String id) {
        this.id = id;
    }
    
    public JdbcConnectionConfiguration getJdbcConnectionConfiguration() {
        return jdbcConnectionConfiguration;
    }
    
    public void setJdbcConnectionConfiguration(JdbcConnectionConfiguration jdbcConnectionConfiguration) {
        this.jdbcConnectionConfiguration = jdbcConnectionConfiguration;
    }
    
    public List<TemplateFactoryConfiguration> getTemplateConfigurations() {
        return templateConfigurations;
    }
    
    public void setTemplateConfigurations(List<TemplateFactoryConfiguration> templateConfigurations) {
        this.templateConfigurations = templateConfigurations;
    }
}
